<template>
  <div class="audiobook-page">
    <!-- 主要内容区域 -->
    <div class="main-content">
      <div class="content-grid">
        <!-- 左侧：文本输入和解析结果 -->
        <div class="left-panel">
          <!-- 文本输入区域 -->
          <div class="text-input-section">
            <div class="section-header">
              <h3 class="section-title">
                文本输入
                <a-tooltip
                  v-model:open="showHelpTooltip"
                  title="由于第三方接口调用成本较高，目前限制上传小说字数为500字。后续将逐步开放字数限制。"
                  placement="right"
                  trigger="click"
                >
                  <HelpCircle
                    :size="16"
                    class="help-icon"
                    @click="showHelpTooltip = !showHelpTooltip"
                  />
                </a-tooltip>
              </h3>
              <div class="char-count">{{ inputText.length }}/500</div>
            </div>
            <a-textarea
              v-model:value="inputText"
              placeholder="请输入小说文本内容..."
              :max-length="500"
              :rows="8"
              class="text-input"
              show-count
            />
            <div class="input-actions">
              <a-button
                type="primary"
                :loading="isParsing"
                :disabled="!inputText.trim() || inputText.length > 500"
                @click="handleParseText"
                class="parse-btn"
              >
                解析文本
              </a-button>
              <a-button
                v-if="parsedSentences.length > 0"
                @click="handleClearAll"
                class="clear-btn"
              >
                清空内容
              </a-button>
            </div>
          </div>

          <!-- 解析结果编辑区域 -->
          <div v-if="parsedSentences.length > 0" class="parsed-results-section">
            <div class="section-header">
              <h3 class="section-title">解析结果</h3>
              <div class="result-actions">
                <a-button size="small" @click="handleConfirmParsed">
                  确定解析结果
                </a-button>
              </div>
            </div>
            <div class="edit-hint">
              <span class="hint-text">💡 提示：您可以直接编辑角色名称和句子内容，修改后点击确认即可</span>
            </div>
            <div class="sentences-list">
              <div
                v-for="(sentence, index) in parsedSentences"
                :key="index"
                class="sentence-item"
              >
                <div class="sentence-header">
                  <span class="sentence-index">{{ index + 1 }}</span>
                  <a-auto-complete
                    v-model:value="sentence.character"
                    :options="characterSuggestions"
                    placeholder="输入角色名称"
                    class="character-input"
                    @blur="handleCharacterChange"
                    @search="handleCharacterSearch"
                  />
                </div>
                <a-textarea
                  v-model:value="sentence.sentence"
                  :rows="2"
                  class="sentence-content"
                />
              </div>
            </div>
          </div>

          <!-- 解析结果占位符 -->
          <div v-else class="parsed-results-placeholder">
            <div class="placeholder-content">
              <div class="placeholder-icon">📝</div>
              <h3 class="placeholder-title">解析结果</h3>
              <p class="placeholder-text">点击"解析文本"按钮后，系统将自动识别文本中的角色对话，您可以在此处编辑和调整解析结果</p>
            </div>
          </div>
        </div>

        <!-- 右侧：角色语音配置 -->
        <div class="right-panel">
          <!-- 角色语音分配 -->
          <div v-if="uniqueCharacters.length > 0" class="character-voices-section">
            <div class="section-header">
              <h3 class="section-title">角色语音配置</h3>
              <div class="character-count">{{ uniqueCharacters.length }} 个角色</div>
            </div>
            <div class="characters-list">
              <div
                v-for="character in uniqueCharacters"
                :key="character"
                class="character-item"
              >
                <div class="character-info">
                  <span class="character-name">{{ character }}</span>
                  <div class="voice-controls">
                    <a-select
                      v-model:value="characterVoices[character]"
                      placeholder="选择语音"
                      class="voice-select"
                      :loading="voicesLoading"
                    >
                      <a-select-option
                        v-for="voice in availableVoices"
                        :key="voice.id"
                        :value="voice.id"
                      >
                        {{ voice.voiceName || voice.name || `语音${voice.id}` }}
                      </a-select-option>
                    </a-select>
                    <span v-if="characterVoices[character]" class="selected-role-display">
                      {{ getRoleNameByRoleId(characterVoices[character]) }}
                    </span>
                    <TTSPlayButton
                      v-if="characterVoices[character]"
                      :message="`你好，我是${character}`"
                      :voice-id="getVoiceIdByRoleId(characterVoices[character])"
                      size="small"
                      :show-text="false"
                      class="voice-preview-btn"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>

          <!-- 角色语音配置占位符 -->
          <div v-else-if="parsedSentences.length === 0" class="character-voices-placeholder">
            <div class="placeholder-content">
              <div class="placeholder-icon">🎭</div>
              <h3 class="placeholder-title">角色语音配置</h3>
              <p class="placeholder-text">请先输入文本并点击"解析文本"按钮，系统将自动识别角色并为您提供语音配置选项</p>
            </div>
          </div>

          <!-- 等待确定解析结果的占位符 -->
          <div v-else class="character-voices-placeholder">
            <div class="placeholder-content">
              <div class="placeholder-icon">⏳</div>
              <h3 class="placeholder-title">等待确定解析结果</h3>
              <p class="placeholder-text">请检查并编辑解析结果，然后点击"确定解析结果"按钮来配置角色语音</p>
            </div>
          </div>
        </div>
      </div>

      <!-- 底部：音频播放器 -->
      <div v-if="confirmedSentences.length > 0" class="player-section">
        <div class="playback-controls">
          <div class="playback-header">
            <h3 class="playback-title">{{ currentTitle }}</h3>
            <div class="playback-progress">
              <span class="current-sentence">{{ currentSentenceIndex + 1 }}</span>
              /
              <span class="total-sentences">{{ confirmedSentences.length }}</span>
            </div>
          </div>
          <div class="playback-actions">
            <a-button
              :type="isPlaying ? 'default' : 'primary'"
              :loading="isGeneratingAudio || isAudioLoading"
              @click="handleTogglePlayback"
              class="play-btn"
            >
              <template #icon>
                <Play v-if="!isPlaying && !isGeneratingAudio && !isAudioLoading" />
                <Pause v-else-if="isPlaying && !isGeneratingAudio && !isAudioLoading" />
              </template>
              {{
                isGeneratingAudio ? '生成中...' :
                isAudioLoading ? '加载中...' :
                isPlaying ? '暂停' : '播放'
              }}
            </a-button>
            <a-button @click="handlePreviousSentence" :disabled="currentSentenceIndex <= 0">
              上一句
            </a-button>
            <a-button @click="handleNextSentence" :disabled="currentSentenceIndex >= confirmedSentences.length - 1">
              下一句
            </a-button>
          </div>
          <div class="current-sentence-display">
            <div class="current-character">
              {{ confirmedSentences[currentSentenceIndex]?.character || '旁白' }}
            </div>
            <div class="current-text">
              {{ confirmedSentences[currentSentenceIndex]?.sentence || '' }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
import { message } from 'ant-design-vue'
import { Play, Pause, HelpCircle } from 'lucide-vue-next'
import { Codewin } from '@/api/generated/Codewin'
import {  useAuthStore } from '@/stores'
import TTSPlayButton from '@/components/Common/TTSPlayButton.vue'
import type { NovelCharacterSentence } from '@/api/generated/data-contracts'

// API 实例
const api = new Codewin({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'https://www.codewin.top',
  securityWorker: (securityData) => {
    return authStore.token ? { headers: { Authorization: `Bearer ${authStore.token}` } } : {}
  }
})

// Store
const authStore = useAuthStore()

// 响应式数据
const inputText = ref('')
const currentTitle = ref('AI听书')
const isParsing = ref(false)
const parsedSentences = reactive<NovelCharacterSentence[]>([])
const confirmedSentences = reactive<NovelCharacterSentence[]>([])
const characterVoices = reactive<Record<string, string>>({})
const currentSentenceIndex = ref(0)
const isPlaying = ref(false)
const isGeneratingAudio = ref(false)
const showHelpTooltip = ref(false)

// 角色相关数据
const availableRoles = ref<any[]>([])
const rolesLoading = ref(false)

// 音频播放相关 - 使用 Web Audio API 处理流式音频
let websocket: WebSocket | null = null
let audioContext: AudioContext | null = null
let audioBuffers: AudioBuffer[] = []
let isAudioPlaying = false
let isAudioLoading = ref(false)
let currentVoiceId = ref('')
const SAMPLE_RATE = 22050
const MIN_BUFFER_SIZE = 4096

// 音频预缓存相关
interface CachedAudio {
  sentenceIndex: number
  audioBuffers: AudioBuffer[]
  isComplete: boolean
  voiceId: string
}

let audioCache = new Map<number, CachedAudio>()
let preloadingSet = new Set<number>() // 正在预加载的句子索引
const MAX_CACHE_SIZE = 3 // 最多缓存3个句子的音频
let currentPlayingSource: AudioBufferSourceNode | null = null
let isWaitingForNextSentence = false

// 角色建议
const characterSuggestions = ref<{ value: string }[]>([])
const allCharactersFromParsed = computed(() => {
  const characters = new Set<string>()
  parsedSentences.forEach(sentence => {
    if (sentence.character && sentence.character.trim()) {
      characters.add(sentence.character.trim())
    }
  })
  return Array.from(characters)
})

// 角色变化处理
const handleCharacterChange = () => {
  // 当角色输入框失去焦点时，自动保存状态
  saveStateToStorage()
}

// 角色搜索建议
const handleCharacterSearch = (searchText: string) => {
  if (!searchText) {
    characterSuggestions.value = [
      { value: '旁白' },
      ...allCharactersFromParsed.value.map(char => ({ value: char }))
    ]
  } else {
    const filtered = allCharactersFromParsed.value
      .filter(char => char.toLowerCase().includes(searchText.toLowerCase()))
      .map(char => ({ value: char }))

    // 如果输入的文本不在现有角色中，添加为新建议
    if (!filtered.some(item => item.value.toLowerCase() === searchText.toLowerCase())) {
      filtered.unshift({ value: searchText })
    }

    characterSuggestions.value = filtered
  }
}

// 初始化音频上下文
const initAudioContext = async (): Promise<boolean> => {
  try {
    if (!audioContext) {
      const ctx = new (window.AudioContext || (window as any).webkitAudioContext)({
        sampleRate: SAMPLE_RATE
      })
      audioContext = ctx
    }

    if (audioContext.state !== 'running') {
      await audioContext.resume()
    }
    return true
  } catch (e) {
    console.error('音频上下文初始化错误:', e)
    return false
  }
}

// 创建WebSocket连接
const createWebSocketConnection = (voiceId: string): Promise<WebSocket> => {
  return new Promise((resolve, reject) => {
    // 检查token是否存在
    if (!authStore.token) {
      reject(new Error('未登录，请先登录后再使用语音功能'))
      return
    }

    // 构建WebSocket URL
    const wsUrl = `wss://www.codewin.top/codewin/ai/speech/tts-stream?token=${encodeURIComponent(authStore.token)}&voice_id=${encodeURIComponent(voiceId)}`

    const ws = new WebSocket(wsUrl)
    ws.binaryType = 'blob'

    ws.onopen = () => {
      resolve(ws)
    }

    ws.onerror = (error) => {
      console.error('WebSocket连接错误:', error)
      reject(new Error('WebSocket连接失败'))
    }

    ws.onclose = (event) => {
      websocket = null
      // 处理认证相关的关闭代码
      if (event.code === 1008 || event.code === 4001) {
        message.error('认证失败，请重新登录')
      }
    }
  })
}

// 计算音频缓冲区总大小
const getTotalBufferSize = (): number => {
  return audioBuffers.reduce((total, buffer) => total + buffer.length, 0)
}

// 处理音频Blob数据
const processAudioBlob = async (blob: Blob) => {
  const reader = new FileReader()
  reader.onload = async (e) => {
    try {
      const result = e.target?.result
      if (!result || !(result instanceof ArrayBuffer)) {
        return
      }

      const arrayBuffer = result as ArrayBuffer
      if (arrayBuffer.byteLength === 0) {
        return
      }

      if (!audioContext || audioContext.state === 'closed') return
      const ctx = audioContext as AudioContext

      // 16位PCM转Float32
      const pcm16 = new Int16Array(arrayBuffer)
      const pcm32 = new Float32Array(pcm16.length)
      for (let i = 0; i < pcm16.length; i++) {
        pcm32[i] = (pcm16[i] || 0) / 32768.0
      }

      // 创建音频缓冲区
      const audioBuf = ctx.createBuffer(1, pcm32.length, SAMPLE_RATE)
      audioBuf.getChannelData(0).set(pcm32)
      audioBuffers.push(audioBuf)

      // 如果缓存足够且未在播放，开始播放
      if (!isAudioPlaying && getTotalBufferSize() >= MIN_BUFFER_SIZE) {
        await playCachedAudio()
      }
    } catch (e) {
      console.error('处理音频数据错误:', e)
    }
  }

  reader.readAsArrayBuffer(blob)
}

// 播放缓存的音频
const playCachedAudio = async (): Promise<void> => {
  if (isAudioPlaying || audioBuffers.length === 0 || !audioContext || audioContext.state === 'closed') {
    return
  }

  const ctx = audioContext as AudioContext

  try {
    isAudioPlaying = true
    isPlaying.value = true
    isAudioLoading.value = false

    // 复制当前缓存到临时数组，避免后续新包干扰正在播放的缓存
    const tempBuffers = [...audioBuffers]
    audioBuffers = [] // 清空全局缓存，准备接收新包

    // 合并临时缓存的所有音频包
    const totalSamples = tempBuffers.reduce((sum, buf) => sum + buf.length, 0)
    const mergedBuf = ctx.createBuffer(1, totalSamples, SAMPLE_RATE)
    const mergedData = mergedBuf.getChannelData(0)

    let writeOffset = 0
    for (const buf of tempBuffers) {
      mergedData.set(buf.getChannelData(0), writeOffset)
      writeOffset += buf.length
    }

    // 创建音频源并播放
    const source = ctx.createBufferSource()
    source.buffer = mergedBuf
    source.connect(ctx.destination)
    currentPlayingSource = source

    // 播放结束后：解锁状态，处理剩余缓存
    source.onended = () => {
      isAudioPlaying = false
      currentPlayingSource = null

      // 若全局缓存仍有新数据，立即继续播放
      if (audioBuffers.length > 0) {
        playCachedAudio()
      } else {
        // 单句播放完成，准备播放下一句
        handleSentencePlaybackComplete()
      }
    }

    try {
      source.start(0)

      // 开始预缓存下一句
      preloadNextSentences()
    } catch (e) {
      console.error(`音频播放启动错误: ${(e as Error).message}`)
      isAudioPlaying = false
      isPlaying.value = false
      isAudioLoading.value = false
      currentPlayingSource = null
      return
    }

  } catch (e) {
    console.error(`播放音频异常: ${(e as Error).message}`)
    isAudioPlaying = false
    isPlaying.value = false
    isAudioLoading.value = false
    currentPlayingSource = null
  }
}

// 处理句子播放完成
const handleSentencePlaybackComplete = () => {
  if (isPlaying.value && !isWaitingForNextSentence) {
    isWaitingForNextSentence = true

    // 检查是否有预缓存的下一句音频
    const nextIndex = currentSentenceIndex.value + 1
    const cachedAudio = audioCache.get(nextIndex)

    if (cachedAudio && cachedAudio.isComplete) {
      playNextSentenceFromCache()
    } else {
      // 给一个小延迟确保状态稳定
      setTimeout(() => {
        if (isPlaying.value && isWaitingForNextSentence) {
          handleNextSentenceImmediate()
        }
        isWaitingForNextSentence = false
      }, 50)
    }
  }
}

// 从缓存播放下一句
const playNextSentenceFromCache = async () => {
  const nextIndex = currentSentenceIndex.value + 1
  if (nextIndex >= confirmedSentences.length) {
    isPlaying.value = false
    isWaitingForNextSentence = false
    message.success('全部播放完成')
    return
  }

  const cachedAudio = audioCache.get(nextIndex)
  if (!cachedAudio || !cachedAudio.isComplete) {
    setTimeout(() => {
      if (isPlaying.value && isWaitingForNextSentence) {
        handleNextSentenceImmediate()
      }
      isWaitingForNextSentence = false
    }, 50)
    return
  }

  try {
    currentSentenceIndex.value = nextIndex
    saveStateToStorage()

    if (!audioContext || audioContext.state === 'closed') {
      throw new Error('音频上下文未初始化')
    }

    const ctx = audioContext as AudioContext

    // 合并缓存的音频包
    const totalSamples = cachedAudio.audioBuffers.reduce((sum, buf) => sum + buf.length, 0)
    const mergedBuf = ctx.createBuffer(1, totalSamples, SAMPLE_RATE)
    const mergedData = mergedBuf.getChannelData(0)

    let writeOffset = 0
    for (const buf of cachedAudio.audioBuffers) {
      mergedData.set(buf.getChannelData(0), writeOffset)
      writeOffset += buf.length
    }

    // 创建音频源并播放
    const source = ctx.createBufferSource()
    source.buffer = mergedBuf
    source.connect(ctx.destination)
    currentPlayingSource = source
    isAudioPlaying = true

    source.onended = () => {
      isAudioPlaying = false
      currentPlayingSource = null
      isWaitingForNextSentence = false

      // 移除已播放的缓存
      audioCache.delete(nextIndex)

      // 继续播放下一句
      handleSentencePlaybackComplete()
    }

    source.start(0)

    // 开始预缓存后续句子
    preloadNextSentences()

  } catch (error) {
    console.error('从缓存播放失败：', error)
    isWaitingForNextSentence = false
    // 回退到正常播放
    setTimeout(() => {
      if (isPlaying.value) {
        handleNextSentenceImmediate()
      }
    }, 50)
  }
}

// 预加载后续句子的音频
const preloadNextSentences = async () => {
  if (!isPlaying.value) return

  const startIndex = currentSentenceIndex.value + 1
  const endIndex = Math.min(startIndex + MAX_CACHE_SIZE, confirmedSentences.length)

  for (let i = startIndex; i < endIndex; i++) {
    // 跳过已缓存或正在预加载的句子
    if (audioCache.has(i) || preloadingSet.has(i)) continue

    const sentence = confirmedSentences[i]
    if (!sentence || !sentence.sentence) continue

    const character = sentence.character || '旁白'
    const roleId = characterVoices[character]

    if (!roleId) continue

    const voiceId = getVoiceIdByRoleId(roleId)
    preloadSentenceAudio(i, sentence.sentence, voiceId)
  }

  // 清理过期的缓存
  cleanupOldCache()
}

// 预加载单个句子的音频
const preloadSentenceAudio = async (sentenceIndex: number, text: string, voiceId: string) => {
  if (preloadingSet.has(sentenceIndex)) return

  preloadingSet.add(sentenceIndex)

  try {
    // 创建独立的WebSocket连接用于预加载
    const preloadWs = await createWebSocketConnection(voiceId)

    const cachedAudio: CachedAudio = {
      sentenceIndex,
      audioBuffers: [],
      isComplete: false,
      voiceId
    }

    audioCache.set(sentenceIndex, cachedAudio)

    // 设置消息处理器
    preloadWs.onmessage = async (event) => {
      try {
        if (event.data instanceof Blob) {
          // 处理音频数据
          const reader = new FileReader()
          reader.onload = async (e) => {
            try {
              const result = e.target?.result
              if (!result || !(result instanceof ArrayBuffer)) return

              const arrayBuffer = result as ArrayBuffer
              if (arrayBuffer.byteLength === 0) return

              if (!audioContext || audioContext.state === 'closed') return
              const ctx = audioContext as AudioContext

              // 16位PCM转Float32
              const pcm16 = new Int16Array(arrayBuffer)
              const pcm32 = new Float32Array(pcm16.length)
              for (let i = 0; i < pcm16.length; i++) {
                pcm32[i] = (pcm16[i] || 0) / 32768.0
              }

              // 创建音频缓冲区
              const audioBuf = ctx.createBuffer(1, pcm32.length, SAMPLE_RATE)
              audioBuf.getChannelData(0).set(pcm32)

              // 添加到缓存
              const cached = audioCache.get(sentenceIndex)
              if (cached) {
                cached.audioBuffers.push(audioBuf)
              }

            } catch (error) {
              console.error('预加载音频数据处理错误:', error)
            }
          }
          reader.readAsArrayBuffer(event.data)

        } else if (typeof event.data === 'string') {
          const data = JSON.parse(event.data)

          if (data.status === 'complete') {
            const cached = audioCache.get(sentenceIndex)
            if (cached) {
              cached.isComplete = true
            }
            preloadWs.close()
          } else if (data.error) {
            console.error(`句子 ${sentenceIndex} 预加载失败:`, data.error)
            audioCache.delete(sentenceIndex)
            preloadWs.close()
          }
        }
      } catch (error) {
        console.error('预加载消息处理错误:', error)
      }
    }

    preloadWs.onerror = (error) => {
      console.error(`句子 ${sentenceIndex} 预加载WebSocket错误:`, error)
      audioCache.delete(sentenceIndex)
    }

    preloadWs.onclose = () => {
      preloadingSet.delete(sentenceIndex)
    }

    // 发送预加载请求
    preloadWs.send(JSON.stringify(text.trim()))

  } catch (error) {
    console.error(`句子 ${sentenceIndex} 预加载失败:`, error)
    preloadingSet.delete(sentenceIndex)
    audioCache.delete(sentenceIndex)
  }
}

// 清理过期的缓存
const cleanupOldCache = () => {
  const currentIndex = currentSentenceIndex.value
  const keysToDelete: number[] = []

  for (const [index] of audioCache) {
    // 删除当前句子之前的缓存（已播放完成）
    if (index < currentIndex) {
      keysToDelete.push(index)
    }
    // 删除距离当前句子太远的缓存
    else if (index > currentIndex + MAX_CACHE_SIZE) {
      keysToDelete.push(index)
    }
  }

  keysToDelete.forEach(key => {
    audioCache.delete(key)
  })
}

const uniqueCharacters = computed(() => {
  const characters = new Set<string>()
  confirmedSentences.forEach(sentence => {
    if (sentence.character && sentence.character.trim()) {
      characters.add(sentence.character.trim())
    }
  })
  return Array.from(characters).sort() // 按字母顺序排序
})

const availableVoices = computed(() => {
  // 从角色数据中提取音色信息，使用角色ID作为选择值
  return availableRoles.value
    .filter(role => role.voiceId) // 只保留有voiceId的角色
    .map(role => ({
      id: role.id, // 使用角色ID作为选择值
      voiceName: role.roleName,
      name: role.roleName,
      voiceId: role.voiceId // 保留voiceId用于后续转换
    }))
})
const voicesLoading = computed(() => rolesLoading.value)

// 根据角色ID获取对应的音色ID
const getVoiceIdByRoleId = (roleId: string): string => {
  const role = availableRoles.value.find(r => r.id === roleId)
  return role?.voiceId || roleId // 如果找不到角色，返回原值作为兜底
}

// 根据角色ID获取角色名称
const getRoleNameByRoleId = (roleId: string): string => {
  const role = availableRoles.value.find(r => r.id === roleId)
  return role?.roleName || roleId // 如果找不到角色，返回原值作为兜底
}

// localStorage 键名
const STORAGE_KEY = 'audiobook-state'

// 持久化状态
interface AudioBookState {
  inputText: string
  parsedSentences: NovelCharacterSentence[]
  confirmedSentences: NovelCharacterSentence[]
  characterVoices: Record<string, string>
  currentSentenceIndex: number
  currentTitle: string
}

// 保存状态到 localStorage
const saveStateToStorage = () => {
  const state: AudioBookState = {
    inputText: inputText.value,
    parsedSentences: parsedSentences.slice(),
    confirmedSentences: confirmedSentences.slice(),
    characterVoices: { ...characterVoices },
    currentSentenceIndex: currentSentenceIndex.value,
    currentTitle: currentTitle.value
  }
  localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
}

// 从 localStorage 恢复状态
const loadStateFromStorage = () => {
  try {
    const stored = localStorage.getItem(STORAGE_KEY)
    if (stored) {
      const state: AudioBookState = JSON.parse(stored)
      inputText.value = state.inputText || ''
      parsedSentences.splice(0, parsedSentences.length, ...(state.parsedSentences || []))
      confirmedSentences.splice(0, confirmedSentences.length, ...(state.confirmedSentences || []))
      Object.assign(characterVoices, state.characterVoices || {})
      currentSentenceIndex.value = state.currentSentenceIndex || 0
      currentTitle.value = state.currentTitle || 'AI听书'
    }
  } catch (error) {
    console.error('Failed to load state from localStorage:', error)
  }
}

// 解析文本
const handleParseText = async () => {
  if (!inputText.value.trim()) {
    message.warning('请输入文本内容')
    return
  }

  try {
    isParsing.value = true
    const response = await api.parseNovelText({
      novelText: inputText.value.trim()
    })

    if (response.data?.code === 0 && response.data.data) {
      parsedSentences.splice(0, parsedSentences.length, ...response.data.data)
      message.success(`文本解析成功，共解析出 ${response.data.data.length} 个句子`)
      saveStateToStorage()
    } else {
      message.error(response.data?.message || '文本解析失败')
    }
  } catch (error) {
    console.error('Parse text error:', error)
    message.error('文本解析失败，请稍后重试')
  } finally {
    isParsing.value = false
  }
}

// 确认解析结果
const handleConfirmParsed = () => {
  if (parsedSentences.length === 0) {
    message.warning('没有可确认的解析结果')
    return
  }

  // 清理和标准化角色名称
  const cleanedSentences = parsedSentences.map(sentence => ({
    ...sentence,
    character: sentence.character?.trim() || '旁白'
  }))

  confirmedSentences.splice(0, confirmedSentences.length, ...cleanedSentences)
  currentSentenceIndex.value = 0

  // 统计角色数量
  const characterCount = uniqueCharacters.value.length
  message.success(`解析结果已确认，共识别出 ${characterCount} 个角色，请为角色配置语音`)
  saveStateToStorage()
}

// 清空所有内容
const handleClearAll = () => {
  inputText.value = ''
  parsedSentences.splice(0)
  confirmedSentences.splice(0)
  Object.keys(characterVoices).forEach(key => delete characterVoices[key])
  currentSentenceIndex.value = 0
  currentTitle.value = 'AI听书'
  isPlaying.value = false
  localStorage.removeItem(STORAGE_KEY)
  message.info('内容已清空')
}

// 播放控制
const handleTogglePlayback = async () => {
  if (isPlaying.value) {
    handlePause()
  } else {
    await handlePlay()
  }
}

const handlePlay = async () => {
  if (confirmedSentences.length === 0) {
    message.warning('没有可播放的内容')
    return
  }

  // 检查语音配置
  const missingVoices = uniqueCharacters.value.filter(char => !characterVoices[char])
  if (missingVoices.length > 0) {
    message.warning(`请为以下角色配置语音：${missingVoices.join(', ')}`)
    return
  }

  try {
    isPlaying.value = true
    isWaitingForNextSentence = false
    await playCurrentSentence()
  } catch (error) {
    console.error('Play error:', error)
    message.error('播放失败')
    isPlaying.value = false
  }
}

const handlePause = () => {
  isPlaying.value = false
  isWaitingForNextSentence = false

  // 停止当前播放的音频
  if (currentPlayingSource) {
    try {
      currentPlayingSource.stop()
    } catch (e) {
      // 停止音频源时出错
    }
    currentPlayingSource = null
  }

  // 停止WebSocket连接
  if (websocket) {
    websocket.close()
    websocket = null
  }

  // 停止音频播放和重置状态
  isAudioPlaying = false
  audioBuffers = []
  currentVoiceId.value = ''

  // 清理预加载状态
  preloadingSet.clear()
  audioCache.clear()

  message.info('播放已暂停')
}

const handlePreviousSentence = () => {
  if (currentSentenceIndex.value > 0) {
    currentSentenceIndex.value--
    saveStateToStorage()
    if (isPlaying.value) {
      playCurrentSentence()
    }
  }
}

const handleNextSentence = () => {
  if (currentSentenceIndex.value < confirmedSentences.length - 1) {
    currentSentenceIndex.value++
    saveStateToStorage()
    if (isPlaying.value) {
      playCurrentSentence()
    }
  } else {
    // 播放完成
    isPlaying.value = false
    message.success('全部播放完成')
  }
}

// 立即播放下一句（优化版本，复用连接）
const handleNextSentenceImmediate = async () => {
  if (currentSentenceIndex.value < confirmedSentences.length - 1) {
    currentSentenceIndex.value++
    saveStateToStorage()

    if (isPlaying.value) {
      const sentence = confirmedSentences[currentSentenceIndex.value]
      if (!sentence || !sentence.sentence) return

      const character = sentence.character || '旁白'
      const roleId = characterVoices[character]

      if (!roleId) {
        message.error(`角色 ${character} 未配置语音`)
        isPlaying.value = false
        return
      }

      const voiceId = getVoiceIdByRoleId(roleId)

      try {
        // 检查是否需要切换语音
        if (voiceId === currentVoiceId.value && websocket && websocket.readyState === WebSocket.OPEN) {
          // 相同语音且连接正常，直接发送新文本
          await callStreamingTTSReuse(sentence.sentence)
        } else {
          // 需要切换语音或重新连接
          await playCurrentSentence()
        }
      } catch (error) {
        console.error('播放下一句失败：', error)
        // 发生错误时不要停止播放，而是重试
        try {
          await playCurrentSentence()
        } catch (retryError) {
          console.error('重试也失败了：', retryError)
          message.error('播放失败，请检查网络连接')
          isPlaying.value = false
        }
      }
    }
  } else {
    // 播放完成
    isPlaying.value = false
    message.success('全部播放完成')
  }
}

// 播放当前句子 - 使用流式音频处理
const playCurrentSentence = async () => {
  const sentence = confirmedSentences[currentSentenceIndex.value]
  if (!sentence || !sentence.sentence) return

  const character = sentence.character || '旁白'
  const roleId = characterVoices[character]

  if (!roleId) {
    message.error(`角色 ${character} 未配置语音`)
    return
  }

  const voiceId = getVoiceIdByRoleId(roleId)

  try {
    isGeneratingAudio.value = true
    isAudioLoading.value = true

    // 停止当前播放的音频和WebSocket连接
    if (websocket) {
      websocket.close()
      websocket = null
    }
    isAudioPlaying = false
    audioBuffers = []

    // 初始化音频上下文
    const audioReady = await initAudioContext()
    if (!audioReady) {
      throw new Error('音频初始化失败')
    }

    // 创建WebSocket连接
    websocket = await createWebSocketConnection(voiceId)
    currentVoiceId.value = voiceId // 记录当前使用的语音ID

    // 使用 WebSocket 进行流式 TTS
    await callStreamingTTS(sentence.sentence)

  } catch (error) {
    console.error('TTS error:', error)
    const errorMessage = error instanceof Error ? error.message : '语音生成失败'
    message.error(errorMessage)
    isPlaying.value = false
  } finally {
    isGeneratingAudio.value = false
    isAudioLoading.value = false
  }
}

// 流式TTS调用
const callStreamingTTS = async (text: string): Promise<void> => {
  return new Promise<void>((resolve, reject) => {
    if (!websocket) {
      reject(new Error('WebSocket未连接'))
      return
    }

    let hasReceivedData = false
    let timeoutId: NodeJS.Timeout | null = null

    // 动态超时机制：如果已经收到数据，延长超时时间
    const setDynamicTimeout = (initialTimeout: number = 30000) => {
      if (timeoutId) {
        clearTimeout(timeoutId)
      }

      timeoutId = setTimeout(() => {
        if (websocket) {
          websocket.close()
        }
        reject(new Error('TTS 生成超时'))
      }, hasReceivedData ? 60000 : initialTimeout) // 收到数据后延长到60秒
    }

    setDynamicTimeout()

    websocket.onmessage = async (event) => {
      try {
        if (event.data instanceof Blob) {
          // 收到音频数据，标记已接收数据并重置超时
          hasReceivedData = true
          setDynamicTimeout(45000) // 重置为45秒超时
          await processAudioBlob(event.data)
        } else if (typeof event.data === 'string') {
          const data = JSON.parse(event.data)

          if (data.status === 'complete') {
            if (timeoutId) {
              clearTimeout(timeoutId)
              timeoutId = null
            }

            // 服务端通知完成后，若仍有缓存未播放，强制触发一次播放
            if (!isAudioPlaying && audioBuffers.length > 0) {
              await playCachedAudio()
            }

            // 等待所有音频播放完成后resolve
            const checkComplete = () => {
              if (!isAudioPlaying && audioBuffers.length === 0) {
                resolve()
              } else {
                setTimeout(checkComplete, 100)
              }
            }
            checkComplete()
          } else if (data.error) {
            if (timeoutId) {
              clearTimeout(timeoutId)
              timeoutId = null
            }
            reject(new Error(data.error))
          }
        }
      } catch (error) {
        console.error('处理 TTS 响应失败：', error)
        if (timeoutId) {
          clearTimeout(timeoutId)
          timeoutId = null
        }
        reject(new Error('处理 TTS 响应失败'))
      }
    }

    websocket.onerror = (error) => {
      console.error('TTS WebSocket错误：', error)
      if (timeoutId) {
        clearTimeout(timeoutId)
        timeoutId = null
      }
      reject(new Error('WebSocket 连接失败'))
    }

    websocket.onclose = (event) => {
      if (timeoutId) {
        clearTimeout(timeoutId)
        timeoutId = null
      }

      // 如果连接意外关闭且没有正常完成，触发错误
      if (event.code !== 1000 && !hasReceivedData) {
        reject(new Error(`WebSocket连接异常关闭 (code: ${event.code})`))
      }
    }

    // 发送TTS请求
    try {
      websocket.send(JSON.stringify(text.trim()))
    } catch (error) {
      if (timeoutId) {
        clearTimeout(timeoutId)
        timeoutId = null
      }
      reject(new Error('发送TTS请求失败'))
    }
  })
}

// 复用连接的流式TTS调用（减少延迟）
const callStreamingTTSReuse = async (text: string): Promise<void> => {
  return new Promise<void>((resolve, reject) => {
    if (!websocket || websocket.readyState !== WebSocket.OPEN) {
      reject(new Error('WebSocket未连接'))
      return
    }

    let hasReceivedData = false
    let timeoutId: NodeJS.Timeout | null = null

    // 动态超时机制
    const setDynamicTimeout = (initialTimeout: number = 30000) => {
      if (timeoutId) {
        clearTimeout(timeoutId)
      }

      timeoutId = setTimeout(() => {
        reject(new Error('TTS 生成超时'))
      }, hasReceivedData ? 60000 : initialTimeout)
    }

    setDynamicTimeout()

    // 重新设置 onmessage 处理器
    websocket.onmessage = async (event) => {
      try {
        if (event.data instanceof Blob) {
          // 收到音频数据，标记已接收数据并重置超时
          hasReceivedData = true
          setDynamicTimeout(45000)
          await processAudioBlob(event.data)
        } else if (typeof event.data === 'string') {
          const data = JSON.parse(event.data)

          if (data.status === 'complete') {
            if (timeoutId) {
              clearTimeout(timeoutId)
              timeoutId = null
            }

            // 服务端通知完成后，若仍有缓存未播放，强制触发一次播放
            if (!isAudioPlaying && audioBuffers.length > 0) {
              await playCachedAudio()
            }

            // 等待所有音频播放完成后resolve
            const checkComplete = () => {
              if (!isAudioPlaying && audioBuffers.length === 0) {
                resolve()
              } else {
                setTimeout(checkComplete, 100)
              }
            }
            checkComplete()
          } else if (data.error) {
            if (timeoutId) {
              clearTimeout(timeoutId)
              timeoutId = null
            }
            reject(new Error(data.error))
          }
        }
      } catch (error) {
        console.error('处理 TTS 响应失败：', error)
        if (timeoutId) {
          clearTimeout(timeoutId)
          timeoutId = null
        }
        reject(new Error('处理 TTS 响应失败'))
      }
    }

    // 发送TTS请求
    try {
      websocket.send(JSON.stringify(text.trim()))
    } catch (error) {
      if (timeoutId) {
        clearTimeout(timeoutId)
        timeoutId = null
      }
      reject(new Error('发送TTS请求失败'))
    }
  })
}


// 获取角色列表
const fetchRoles = async () => {
  rolesLoading.value = true
  try {
    // 并发获取用户角色和默认角色
    const [userRolesResponse, defaultRolesResponse] = await Promise.all([
      api.listRoles({
        rolePageSearchDto: {
          pageNo: 1,
          pageSize: 100
        }
      }),
      api.listDefaultRoles({
        rolePageSearchDto: {
          pageNo: 1,
          pageSize: 100
        }
      })
    ])
    
    // 合并角色数据 - 修正数据结构访问
    const userRoles = userRolesResponse.data?.data?.list || []
    const defaultRoles = defaultRolesResponse.data?.data?.list || []
    
    // 去除重复角色（基于角色ID去重）
    const allRoles = [...userRoles, ...defaultRoles]
    const uniqueRoles = allRoles.filter((role, index, self) => 
      index === self.findIndex(r => r.id === role.id)
    )
    availableRoles.value = uniqueRoles
  } catch (error) {
    console.error('Failed to fetch roles:', error)
    message.error('获取角色列表失败')
  } finally {
    rolesLoading.value = false
  }
}

// 监听数据变化，自动保存状态
watch([inputText, parsedSentences, confirmedSentences, characterVoices, currentSentenceIndex], () => {
  saveStateToStorage()
}, { deep: true })

// 组件挂载时初始化
onMounted(async () => {
  // 加载保存的状态
  loadStateFromStorage()

  // 初始化角色建议
  characterSuggestions.value = [
    { value: '旁白' },
    ...allCharactersFromParsed.value.map(char => ({ value: char }))
  ]

  // 获取可用角色列表
  try {
    await fetchRoles()
  } catch (error) {
    console.error('Failed to fetch roles:', error)
  }
})

// 组件卸载时清理
onUnmounted(() => {
  // 停止播放和WebSocket连接
  if (websocket) {
    websocket.close()
    websocket = null
  }

  // 停止当前播放的音频
  if (currentPlayingSource) {
    try {
      currentPlayingSource.stop()
    } catch (e) {
      // 停止音频源时出错
    }
    currentPlayingSource = null
  }

  // 关闭音频上下文
  if (audioContext) {
    audioContext.close()
    audioContext = null
  }

  // 清理音频缓存
  audioBuffers = []
  isAudioPlaying = false

  // 重置状态
  isPlaying.value = false
  isGeneratingAudio.value = false
  isAudioLoading.value = false
  currentVoiceId.value = ''
})
</script>

<style scoped>
.audiobook-page {
  min-height: 100vh;
  position: relative;
}

.main-content {
  max-width: 1200px;
  margin: 0 auto;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  background: #f8faff;
}

.content-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
  align-items: start;
}

.left-panel {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.right-panel {
  position: sticky;
  top: 24px;
}

/* 文本输入区域 */
.text-input-section {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  border: 1px solid #e2e8f0;
}

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.section-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: #1e293b;
  display: flex;
  align-items: center;
  gap: 8px;
}

.help-icon {
  color: #64748b;
  cursor: pointer;
  transition: color 0.2s ease;
}

.help-icon:hover {
  color: #3b82f6;
}

.char-count {
  font-size: 12px;
  color: #64748b;
  background: #f1f5f9;
  padding: 4px 8px;
  border-radius: 6px;
}

.text-input {
  margin-bottom: 16px;
}

.input-actions {
  display: flex;
  gap: 12px;
}

.parse-btn {
  background: linear-gradient(135deg, #3b82f6, #1d4ed8);
  border: none;
  color: white;
  font-weight: 500;
}

.parse-btn:hover {
  background: linear-gradient(135deg, #2563eb, #1e40af);
}

.clear-btn {
  color: #ef4444;
  border-color: #ef4444;
}

.clear-btn:hover {
  background: #fef2f2;
  border-color: #dc2626;
  color: #dc2626;
}

/* 解析结果区域 */
.parsed-results-section {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  border: 1px solid #e2e8f0;
}

.result-actions {
  display: flex;
  gap: 8px;
}

.edit-hint {
  background: #f0f9ff;
  border: 1px solid #bae6fd;
  border-radius: 6px;
  padding: 12px;
  margin-bottom: 16px;
}

.hint-text {
  font-size: 13px;
  color: #0369a1;
  line-height: 1.4;
}

.sentences-list {
  max-height: 400px;
  overflow-y: auto;
  margin-top: 16px;
}

.sentence-item {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 12px;
  transition: all 0.2s ease;
}

.sentence-item:hover {
  border-color: #3b82f6;
  box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
}

.sentence-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
}

.sentence-index {
  background: #3b82f6;
  color: white;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  flex-shrink: 0;
}

.character-input {
  width: 120px;
  font-size: 12px;
}

.sentence-content {
  border: 1px solid #e2e8f0;
  border-radius: 6px;
}

/* 角色语音配置区域 */
.character-voices-section {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  border: 1px solid #e2e8f0;
}

.character-count {
  font-size: 12px;
  color: #64748b;
  background: #f1f5f9;
  padding: 4px 8px;
  border-radius: 6px;
}

.characters-list {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.character-item {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 16px;
  transition: all 0.2s ease;
}

.character-item:hover {
  border-color: #3b82f6;
  box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
}

.character-info {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.character-name {
  font-weight: 500;
  color: #1e293b;
  font-size: 14px;
}

.voice-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.voice-select {
  flex: 1;
  min-width: 0;
}

.selected-role-display {
  font-size: 12px;
  color: #666;
  font-style: italic;
}

.voice-preview-btn {
  flex-shrink: 0;
}

/* 播放器区域 */
.player-section {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  border: 1px solid #e2e8f0;
}

.playback-controls {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.playback-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.playback-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: #1e293b;
}

.playback-progress {
  font-size: 14px;
  color: #64748b;
  background: #f1f5f9;
  padding: 6px 12px;
  border-radius: 8px;
}

.current-sentence {
  font-weight: 600;
  color: #3b82f6;
}

.playback-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: center;
}

.play-btn {
  background: linear-gradient(135deg, #10b981, #059669);
  border: none;
  color: white;
  font-weight: 500;
  min-width: 100px;
}

.play-btn:hover {
  background: linear-gradient(135deg, #059669, #047857);
}

.current-sentence-display {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 16px;
}

.current-character {
  font-size: 12px;
  font-weight: 600;
  color: #3b82f6;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 8px;
}

.current-text {
  font-size: 16px;
  line-height: 1.6;
  color: #1e293b;
}

/* 响应式设计 */
@media (max-width: 1024px) {
  .content-grid {
    grid-template-columns: 1fr;
    gap: 20px;
  }

  .right-panel {
    position: static;
  }
}

@media (max-width: 768px) {
  .main-content {
    padding: 16px;
    gap: 16px;
  }

  .text-input-section,
  .parsed-results-section,
  .character-voices-section,
  .player-section {
    padding: 16px;
  }

  .playback-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .playback-actions {
    flex-wrap: wrap;
    justify-content: flex-start;
  }
}

@media (max-width: 640px) {
  .input-actions {
    flex-direction: column;
  }

  .voice-controls {
    flex-direction: column;
    align-items: stretch;
  }

  .sentence-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }

  .character-input {
    width: 100%;
  }
}

/* 动画效果 */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.text-input-section,
.parsed-results-section,
.character-voices-section,
.player-section {
  animation: fadeInUp 0.6s ease-out;
}

.sentence-item,
.character-item {
  animation: fadeInUp 0.4s ease-out;
}

.sentence-item:nth-child(even) {
  animation-delay: 0.1s;
}

.character-item:nth-child(even) {
  animation-delay: 0.1s;
}

/* 滚动条样式 */
.sentences-list::-webkit-scrollbar {
  width: 6px;
}

.sentences-list::-webkit-scrollbar-track {
  background: #f1f5f9;
  border-radius: 3px;
}

.sentences-list::-webkit-scrollbar-thumb {
  background: #cbd5e1;
  border-radius: 3px;
}

.sentences-list::-webkit-scrollbar-thumb:hover {
  background: #94a3b8;
}

/* 解析结果占位符样式 */
.parsed-results-placeholder {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  border: 1px solid #e2e8f0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 200px;
}

.placeholder-content {
  text-align: center;
  max-width: 300px;
}

.placeholder-icon {
  font-size: 48px;
  margin-bottom: 16px;
  opacity: 0.6;
}

.placeholder-title {
  margin: 0 0 12px 0;
  font-size: 16px;
  font-weight: 600;
  color: #1e293b;
}

.placeholder-text {
  margin: 0;
  font-size: 14px;
  color: #64748b;
  line-height: 1.5;
}

/* 角色语音配置占位符样式 */
.character-voices-placeholder {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  border: 1px solid #e2e8f0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 调整高度，使其与左侧模块更匹配 */
  min-height: 500px;
  height: fit-content;
}

/* 调整角色语音配置区域高度 */
.character-voices-section {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  border: 1px solid #e2e8f0;
  /* 调整高度，使其与左侧模块更匹配 */
  min-height: 500px;
  height: fit-content;
  display: flex;
  flex-direction: column;
}

/* 确保角色列表可以滚动 */
.characters-list {
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1;
  overflow-y: auto;
  max-height: 400px;
}

.character-item {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 16px;
  transition: all 0.2s ease;
}

.character-item:hover {
  border-color: #3b82f6;
  box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
}

.character-info {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.character-name {
  font-weight: 500;
  color: #1e293b;
  font-size: 14px;
}

.voice-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}

.voice-select {
  flex: 1;
  min-width: 0;
}

.voice-preview-btn {
  flex-shrink: 0;
}

/* 播放器区域 */
.player-section {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  border: 1px solid #e2e8f0;
}

.playback-controls {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.playback-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.playback-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: #1e293b;
}

.playback-progress {
  font-size: 14px;
  color: #64748b;
  background: #f1f5f9;
  padding: 6px 12px;
  border-radius: 8px;
}

.current-sentence {
  font-weight: 600;
  color: #3b82f6;
}

.playback-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: center;
}

.play-btn {
  background: linear-gradient(135deg, #10b981, #059669);
  border: none;
  color: white;
  font-weight: 500;
  min-width: 100px;
}

.play-btn:hover {
  background: linear-gradient(135deg, #059669, #047857);
}

.current-sentence-display {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 16px;
}

.current-character {
  font-size: 12px;
  font-weight: 600;
  color: #3b82f6;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 8px;
}

.current-text {
  font-size: 16px;
  line-height: 1.6;
  color: #1e293b;
}

/* 响应式设计 */
@media (max-width: 1024px) {
  .content-grid {
    grid-template-columns: 1fr;
    gap: 20px;
  }

  .right-panel {
    position: static;
  }
}

@media (max-width: 768px) {
  .main-content {
    padding: 16px;
    gap: 16px;
  }

  .text-input-section,
  .parsed-results-section,
  .character-voices-section,
  .player-section {
    padding: 16px;
  }

  .playback-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }

  .playback-actions {
    flex-wrap: wrap;
    justify-content: flex-start;
  }
}

@media (max-width: 640px) {
  .input-actions {
    flex-direction: column;
  }

  .voice-controls {
    flex-direction: column;
    align-items: stretch;
  }

  .sentence-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }

  .character-input {
    width: 100%;
  }
}

/* 动画效果 */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.text-input-section,
.parsed-results-section,
.character-voices-section,
.player-section {
  animation: fadeInUp 0.6s ease-out;
}

.sentence-item,
.character-item {
  animation: fadeInUp 0.4s ease-out;
}

.sentence-item:nth-child(even) {
  animation-delay: 0.1s;
}

.character-item:nth-child(even) {
  animation-delay: 0.1s;
}

/* 滚动条样式 */
.sentences-list::-webkit-scrollbar {
  width: 6px;
}

.sentences-list::-webkit-scrollbar-track {
  background: #f1f5f9;
  border-radius: 3px;
}

.sentences-list::-webkit-scrollbar-thumb {
  background: #cbd5e1;
  border-radius: 3px;
}

.sentences-list::-webkit-scrollbar-thumb:hover {
  background: #94a3b8;
}
</style>
